﻿///<copyright>
/// Copyright © 2009 Stephen Styrchak. All Rights Reserved.
///</copyright>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Ferpect.ComponentModel.Design;
#if WINDOWS
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
#endif

namespace Ferpect.ComponentModel
{
#if WINDOWS
    [ToolboxItemFilter("Ferpect.ComponentModel.Screen", ToolboxItemFilterType.Allow)]
    [DesignerSerializer(Serializers.ScreenManagerTypeSerializer, typeof(TypeCodeDomSerializer))]
    [DesignerSerializer(Serializers.ScreenManagerControlSerializer, typeof(CodeDomSerializer))]
    [Designer(Designers.ScreenManagerDesigner, typeof(IRootDesigner))]
#endif
    [ViewDocumentDesigner(Designers.ScreenManagerDesigner)]
    [GameService(typeof(IScreenManager))]
    public class ScreenManager : ViewComponent, IScreenManager
    {
        private IGameLoop gameLoop;
        private IScreen startScreen;
        private IScreen mainScreen;
        private IScreen exitScreen;
        private Stack<IScreen> activeScreens;
        private ScreenCollection screenCollection;
        private AmbientProperties ambientProperties;
        private bool started;

        public ScreenManager()
        {
            this.activeScreens = new Stack<IScreen>();
            this.screenCollection = new ScreenCollection(this);
            this.ambientProperties = new AmbientProperties();
        }

        public override void Initialize()
        {
            if (this.Site != null)
            {
                this.gameLoop = (IGameLoop)this.Site.GetService(typeof(IGameLoop));

                this.gameLoop.AddComponent(this.ambientProperties);
                this.gameLoop.Services.AddService(typeof(AmbientProperties), this.ambientProperties);

                this.Content = this.gameLoop.Content;
            }

            base.Initialize();
            this.screenCollection.Initialize();
        }

        public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            if (!this.started)
            {
                this.Start();
            }
        }

        /// <summary>
        /// Activate the startup screen. If no startup screen is specified, the screen
        /// manager will simply exit.
        /// </summary>
        private void Start()
        {
            if (this.startScreen == null)
            {
                Debug.WriteLine("ScreenManager.StartScreen is null. The screen manager will exit the game immediately when there is no start screen.");
                this.gameLoop.Exit();
            }
            else
            {
                this.activeScreens.Push(this.startScreen);
                this.startScreen.Activate(new ScreenContext(null, 0));
            }
            this.started = true;
        }

        private void OnBackColorChanged(object sender, EventArgs args)
        {
            bool foundSender = false;
            bool covering = false;
            foreach (IScreen screen in this.activeScreens)
            {
                if (Object.ReferenceEquals(screen, sender))
                {
                    foundSender = true;
                    covering = screen.BackColor.HasValue;
                    continue;
                }
                if (foundSender)
                {
                    if (covering)
                    {
                        screen.Hide();
                    }
                    else
                    {
                        screen.Show();
                    }
                }
                if (screen.BackColor.HasValue)
                {
                    break;
                }
            }
        }

        #region IScreenManager Members

        public IScreen StartScreen
        {
            get
            {
                return this.startScreen;
            }
            set
            {
                this.startScreen = value;
            }
        }

        public IScreen MainScreen
        {
            get
            {
                return this.mainScreen;
            }
            set
            {
                this.mainScreen = value;
            }
        }

        public IScreen ExitScreen
        {
            get
            {
                return this.exitScreen;
            }
            set
            {
                this.exitScreen = value;
            }
        }

        public void Goto(string nextScreen, int suggestedDrawOrder)
        {
            IScreen next = this.exitScreen;
            if (!String.IsNullOrEmpty(nextScreen))
            {
                next = this.screenCollection[nextScreen];
            }
            IScreen current = this.activeScreens.Pop();
            current.BackColorChanged -= this.OnBackColorChanged;
            foreach (IScreen activeScreen in this.activeScreens)
            {
                activeScreen.Exit();
            }
            this.activeScreens.Clear();
            this.Transition(current, true, next, true, suggestedDrawOrder);
        }

        public void Call(string nextScreen, int suggestedDrawOrder, bool transitionOff)
        {
            IScreen next = this.screenCollection[nextScreen];
            IScreen current = this.activeScreens.Peek();
            this.Transition(current, false, next, true, suggestedDrawOrder);
        }

        public void Return()
        {
            IScreen current = this.activeScreens.Pop();
            current.BackColorChanged -= this.OnBackColorChanged;
            IScreen next = this.exitScreen;
            if (this.activeScreens.Count > 0)
            {
                next = this.activeScreens.Peek();
            }
            this.Transition(current, true, next, false, 0);
        }

        private void Transition(IScreen current, bool shouldExit, IScreen next, bool pushNext, int suggestedDrawOrder)
        {
            if (shouldExit)
            {
                current.Exit();
                this.RevealScreens();
            }
            else
            {
                current.SetFocus(false);
            }
            if (next != null)
            {
                if (pushNext)
                {
                    this.activeScreens.Push(next);
                    next.BackColorChanged += this.OnBackColorChanged;
                    next.Activate(new ScreenContext(current, suggestedDrawOrder));
                    if (next.BackColor.HasValue)
                    {
                        this.OnBackColorChanged(next, EventArgs.Empty);
                    }
                }
                else
                {
                    next.SetFocus(true);
                }
            }
            else
            {
                this.gameLoop.Exit();
            }
        }

        public void ReturnAndCall(string nextScreen, int suggestedDrawOrder)
        {
            IScreen next = this.screenCollection[nextScreen];
            IScreen current = this.activeScreens.Pop();
            current.BackColorChanged -= this.OnBackColorChanged;
            if (current.BackColor.HasValue && !next.BackColor.HasValue)
            {
                this.RevealScreens();
            }
            this.Transition(current, true, next, true, suggestedDrawOrder);
        }

        private void RevealScreens()
        {
            // The screen we're removing was covering the screens below.
            foreach (IScreen coveredScreen in this.activeScreens)
            {
                coveredScreen.Show();
                if (coveredScreen.BackColor.HasValue)
                {
                    break;
                }
            }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ScreenCollection Screens
        {
            get { return this.screenCollection; }
        }

        #endregion
    }
}
